package functionroots.methods;

import functionroots.primitives.MaximumIterationsException;
import functionroots.primitives.RealFunction;
import functionroots.primitives.RootFinder;

/**
 * Provê uma abstração dos métodos iterativos (ou métodos de ponto fixo) para a
 * obtenção de zeros de funções de uma variável real. Métodos iterativos devem
 * possuir uma função de iteração, que fornece uma nova aproximação a partir da
 * atual.
 * @author orion
 */
public class FixedPoint<D extends PointData> extends RootFinder<D> {

    private RealFunction iterationFunction;

    /**
     * Instancia um método iterativo com uma função, precisão e número máximo de
     * iterações pré-definidos.
     * @param function a função alvo.
     * @param precision a precisão pretendida.
     * @param maxIterations o número máximo de iterações.
     */
    public FixedPoint(RealFunction function, RealFunction iterationFunction,
            double precision, long maxIterations) {
        super(function, precision, maxIterations);
        if (iterationFunction == null) {
            throw new IllegalArgumentException("Iteration function cannot be null!");
        } else {
            this.iterationFunction = iterationFunction;
        }
    }

    /**
     * Instancia um método iterativo com uma função e precisão pré-definidas.
     * @param function a função alvo.
     * @param precision a precisão pretendida.
     */
    public FixedPoint(RealFunction function, RealFunction iterationFunction,
            double precision) {
        super(function, precision);
        if (iterationFunction == null) {
            throw new IllegalArgumentException("Iteration function cannot be null!");
        } else {
            this.iterationFunction = iterationFunction;
        }
    }

    /**
     * Tenta achar a melhor aproximação possível de uma raiz a partir do ponto
     * inicial fornecido. O número máximo de iterações pode impedir que precisão
     * desejada seja alcançada.
     * @param x Aproximação inicial.
     * @return A melhor aproximação possível de uma raiz a partir do aproximação
     * inicial.
     */
    @Override
    public double scan(D data) throws MaximumIterationsException {
        if (iterationFunction == null) {
            throw new IllegalStateException("Iteration function is not set!");
        }
        double x = data.x;
        if (Math.abs(getFunction().value(x)) < getPrecision()) {
            return x;
        }
        double newX;
        for (int i = 0; i < getMaxIterations(); i++) {
            newX = iterationFunction.value(x);
            if (Math.abs(getFunction().value(newX)) < getPrecision() || Math.abs(newX - x) < getPrecision()) {
                return newX;
            } else {
                x = newX;
            }
        }
        throw new MaximumIterationsException(this, data, x);
    }

    public RealFunction getIterationFunction() {
        return iterationFunction;
    }

    public void setIterationFunction(RealFunction iterationFunction) {
        this.iterationFunction = iterationFunction;
    }
}
